// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: wsfuyibing <682805@qq.com>
// Date: 2024-07-18

package src

import (
	"regexp"
	"strings"
)

var (
	RegexArgumentMatchCommand        = regexp.MustCompile(`^[a-zA-Z][-_a-zA-Z0-9:.]*$`)
	RegexArgumentMatchOption         = regexp.MustCompile(`^-`)
	RegexArgumentMatchOptionWithFull = regexp.MustCompile(`^(-+)([a-zA-Z0-9][-_a-zA-Z0-9.]*)=(.+)`)
	RegexArgumentMatchOptionWithName = regexp.MustCompile(`^(-+)([a-zA-Z0-9][-_a-zA-Z0-9.]*)$`)

	singletonArgument *Argument
)

// Argument
// is a component used to parse command-line arguments.
type Argument struct {
	commandName, scriptPath string
	optionMapper            map[string]string
}

// NewArgument
// returns a singleton instance.
func NewArgument() *Argument {
	return singletonArgument
}

// GetCommandName
// returns a command name that defined in argument.
//
// Usage:
//   go run main.go example
//
// Output:
//   example
func (o *Argument) GetCommandName() string {
	return o.commandName
}

// GetOption
// returns an option value with given name if exists.
//
// Usage:
//   go run main.go example --key=value
//
// Code:
//   key, has := console.GetOption("key")
//
// Output:
//   "value", true
func (o *Argument) GetOption(name string) (str string, has bool) {
	str, has = o.optionMapper[name]
	return
}

// GetOptions
// returns all options mapper.
//
// Output:
//   {
//       "key": "value"
//   }
func (o *Argument) GetOptions() map[string]string {
	return o.optionMapper
}

// GetScriptPath
// returns a real script path that called in argument.
//
// Usage: (compiled)
//   /data/sketch/app example
//
// Output:
//   /data/sketch/app
func (o *Argument) GetScriptPath() string {
	return o.scriptPath
}

func (o *Argument) GetScriptExecutor() string {
	return "go run main.go"
}

// Parse
// parses specified argument list given args.
func (o *Argument) Parse(args []string) {
	var last = ""

	// Range args from the parameters list.
	for i, s := range args {
		// Ignore empty string.
		if s = strings.TrimSpace(s); s == "" {
			continue
		}
		// Script path from the first arg.
		if i == 0 {
			o.scriptPath = s
			continue
		}
		// Value of the option.
		if !RegexArgumentMatchOption.MatchString(s) {
			// Bind on the option.
			if last != "" {
				o.optionMapper[last] = s
				last = ""
				continue
			}
			// Generate as command name.
			if o.commandName == "" && RegexArgumentMatchCommand.MatchString(s) {
				o.commandName = s
			}
			continue
		}
		// Matched option definition as key/value pair.
		if m := RegexArgumentMatchOptionWithFull.FindStringSubmatch(s); len(m) == 4 {
			ck := m[2]
			cv := m[3]

			if c1 := m[1]; c1 != "-" {
				// Example as follows:
				// --key="value"
				o.optionMapper[ck] = cv
			} else {
				// Example as follows:
				// -k="value"
				// -key="value"
				cl := len(ck)
				for ci, c := range ck {
					if (ci + 1) == cl {
						o.optionMapper[string(c)] = cv
					} else {
						o.optionMapper[string(c)] = ""
					}
				}
			}
			continue
		}
		// Matched option definition as single.
		if m := RegexArgumentMatchOptionWithName.FindStringSubmatch(s); len(m) == 3 {
			ck := m[2]
			if c1 := m[1]; c1 != "-" {
				// Example as follows:
				// --key
				o.optionMapper[ck] = ""
				last = ck
			} else {
				// Example as follows:
				// -k
				// -key
				cl := len(ck)
				for ci, c := range ck {
					cs := string(c)
					o.optionMapper[cs] = ""
					if (ci + 1) == cl {
						last = cs
					}
				}
			}
			continue
		}
	}
}

// +---------------------------------------------------------------------------+
// | Access methods                                                            |
// +---------------------------------------------------------------------------+

func (o *Argument) init() *Argument {
	o.optionMapper = make(map[string]string)
	return o
}
